package io.persimmon.springboot.trace;

import io.persimmon.core.module.log.CostLog;
import io.persimmon.core.module.trace.Trace;
import io.persimmon.core.module.trace.TraceContext;
import io.persimmon.core.module.trace.TraceManager;
import io.persimmon.springboot.aop.MethodAopInfo;
import io.persimmon.springboot.trace.annotation.Traceable;
import io.persimmon.springboot.trace.bo.TraceHandler;
import io.persimmon.springboot.trace.bo.TraceMethodContext;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Objects;
import java.util.Optional;

@Component
@Aspect
@Slf4j
public class TraceMethodAOP {

    @Autowired(required = false)
    private TraceHandler traceHandler;

    @Pointcut("@annotation(io.persimmon.springboot.trace.annotation.Traceable)")
    public void executeService() {

    }

    @Around("executeService()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        if (Objects.isNull(traceHandler)) {
            return proceedingJoinPoint.proceed();
        }
        try {
            MethodAopInfo methodAopInfo = new MethodAopInfo(proceedingJoinPoint);
            String traceName = getTraceName(methodAopInfo);
            TraceContext traceContext = TraceManager.createTrace(traceName);

            return CostLog.create(() -> methodAopInfo.invoke())
                    .execute()
                    .postExecute(r -> {
                        Trace.log(log, "cost:{}", r.getCost());
                        traceHandler.handleMethod(new TraceMethodContext(traceContext, methodAopInfo));
                        TraceManager.removeTrace(traceContext);
                    })
                    .getResult();
        } catch (Exception e) {
            return proceedingJoinPoint.proceed();
        }
    }

    private String getTraceName(MethodAopInfo methodAopInfo) {
        Traceable annotation = methodAopInfo.getMethod().getAnnotation(Traceable.class);
        return Optional.ofNullable(annotation).map(Traceable::value).orElse(methodAopInfo.getMethod().getName());
    }

}
